package org.example.controller;

import com.google.gson.Gson;
import com.yubico.webauthn.AssertionRequest;
import com.yubico.webauthn.AssertionResult;
import com.yubico.webauthn.FinishAssertionOptions;
import com.yubico.webauthn.FinishRegistrationOptions;
import com.yubico.webauthn.RegistrationResult;
import com.yubico.webauthn.RelyingParty;
import com.yubico.webauthn.data.AttestationConveyancePreference;
import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.COSEAlgorithmIdentifier;
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
import com.yubico.webauthn.data.PublicKeyCredential;
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
import com.yubico.webauthn.data.PublicKeyCredentialParameters;
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
import com.yubico.webauthn.data.PublicKeyCredentialType;
import com.yubico.webauthn.data.RelyingPartyIdentity;
import com.yubico.webauthn.data.UserIdentity;
import com.yubico.webauthn.data.UserVerificationRequirement;
import lombok.extern.slf4j.Slf4j;
import org.example.config.WebAuthnProperties;
import org.example.dto.Form;
import org.example.dto.LoginForm;
import org.example.service.PasskeyRepository;
import org.example.service.UserService;
import org.example.vo.ResultVo;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.thymeleaf.util.StringUtils;

import javax.annotation.Resource;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/passkey")
@Slf4j
public class PasskeyController {

    @Resource
    private UserService userService;

    @Resource
    private PasskeyRepository passkeyRepository;

    @Resource
    private WebAuthnProperties webAuthnProperties;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RelyingParty relyingParty;

    /**
     * webauthn 绑定选项
     */
    @GetMapping("/register/options")
    public ResultVo beginRegistration(Integer id) throws Exception {
        // Create challenge
        byte[] challenge = new byte[32];
        new SecureRandom().nextBytes(challenge);
        ByteArray idByteArray = new ByteArray(ByteBuffer.allocate(Integer.BYTES).putInt(id).array());
        String redisKey = String.format("REGISTER:%d", id);
        // Build options
        Set<PublicKeyCredentialDescriptor> excludeCredentials = passkeyRepository.getCredentialIdsForUsername(id + "");
        PublicKeyCredentialCreationOptions options = PublicKeyCredentialCreationOptions.builder()
                .rp(RelyingPartyIdentity.builder().id(webAuthnProperties.getDomain()).name(webAuthnProperties.getName()).build())
                .user(UserIdentity.builder()
                        .name(id + "")
                        .displayName(id + "")
                        //作为userHandle的字节数组形式
                        .id(idByteArray)
                        .build())
                .challenge(new ByteArray(challenge))
                .pubKeyCredParams(List.of(PublicKeyCredentialParameters.builder().alg(COSEAlgorithmIdentifier.ES256).type(PublicKeyCredentialType.PUBLIC_KEY).build()))
                .timeout(300000L)
                .attestation(AttestationConveyancePreference.NONE)
                .excludeCredentials(excludeCredentials)
                .build();
        stringRedisTemplate.opsForValue().set(redisKey, new Gson().toJson(options), 5, TimeUnit.MINUTES);
        return new ResultVo<>(options);
    }

    /**
     * webauthn 绑定
     */
    @PostMapping("/register/complete")
    public ResultVo completeRegistration(@RequestBody Form form) {
        try {
            log.info("webauthn 绑定 开始{}", form);
            String redisKey = String.format("REGISTER:%d", form.getUserId());
            String optionsJson = stringRedisTemplate.opsForValue().get(redisKey);
            if (optionsJson == null) {
                log.info("webauthn 绑定 redis 信息不存在");
                return new ResultVo("fail");
            }
            PublicKeyCredentialCreationOptions request = new Gson().fromJson(optionsJson, PublicKeyCredentialCreationOptions.class);
            // 手动构建注册响应
            AuthenticatorAttestationResponse attestationResponse = AuthenticatorAttestationResponse.builder()
                    .attestationObject(new ByteArray(Base64.getUrlDecoder().decode(form.getResponse().getAttestationObject())))
                    .clientDataJSON(new ByteArray(Base64.getUrlDecoder().decode(form.getResponse().getClientDataJSON())))
                    .build();
            ClientRegistrationExtensionOutputs clientExtensionOutputs = ClientRegistrationExtensionOutputs.builder().build();
            // 构建 PublicKeyCredential
            PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> credential =
                    PublicKeyCredential.<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>builder()
                            .id(new ByteArray(Base64.getUrlDecoder().decode(form.getId())))
                            .response(attestationResponse)
                            .clientExtensionResults(clientExtensionOutputs)
                            .type(PublicKeyCredentialType.PUBLIC_KEY)
                            .build();
            RegistrationResult result = relyingParty.finishRegistration(FinishRegistrationOptions.builder()
                    .request(request)
                    .response(credential)
                    .build());
            userService.bind(form.getUserId(), result.getKeyId().getId().getBase64Url(), result.getPublicKeyCose().getBase64Url());
            return new ResultVo("success");
        } catch (Exception e) {
            log.info("失败", e);
        }
        return new ResultVo("fail");
    }


    /**
     * 登录选项
     */
    @GetMapping("/login/options")
    public ResultVo beginLogin(Integer unionId) {
        byte[] challenge = new byte[32];
        new SecureRandom().nextBytes(challenge);
        String challengeBase64Url = Base64.getUrlEncoder().withoutPadding().encodeToString(challenge);
        String redisKey = String.format("LOGIN:%d",unionId);
        // 持久化 AssertionRequest 对象（建议用 challenge 做 key）
        stringRedisTemplate.opsForValue().set(
                redisKey,
                challengeBase64Url,
                5, TimeUnit.MINUTES
        );

        PublicKeyCredentialRequestOptions options = PublicKeyCredentialRequestOptions.builder()
                .challenge(new ByteArray(challenge))
                .rpId(webAuthnProperties.getDomain())
                .timeout(300000L)
                .userVerification(UserVerificationRequirement.REQUIRED)
                .allowCredentials(Collections.emptyList()) // 空代表让客户端发现所有discoverable credential
                .build();
        return new ResultVo(options);
    }

    /**
     * 登录
     */
    @PostMapping("/login/complete")
    public ResultVo completeLogin(@RequestBody LoginForm form) {
        log.info("webauthn 登录 开始");
        String redisKey = String.format("LOGIN:%d",form.getUnionId());
        String challengeBase64Url = stringRedisTemplate.opsForValue().get(redisKey);
        if(StringUtils.isEmpty(challengeBase64Url)){
            log.info("webauthn 登录 challengeBase64Url is blank");
            return new ResultVo("fail");
        }
        try{

            ByteArray challenge = ByteArray.fromBase64Url(challengeBase64Url);
            PublicKeyCredentialRequestOptions requestOptions = PublicKeyCredentialRequestOptions.builder()
                    .challenge(challenge)
                    .rpId(webAuthnProperties.getDomain())
                    .userVerification(UserVerificationRequirement.PREFERRED)
                    .allowCredentials(Collections.emptyList())
                    .build();
            AssertionRequest assertionRequest = AssertionRequest.builder()
                    .publicKeyCredentialRequestOptions(requestOptions)
                    .build();
            // 手动构建 AuthenticatorAssertionResponse
            byte[] clientDataJsonBytes = Base64.getUrlDecoder().decode(form.getResponse().getClientDataJSON());
            byte[] authenticatorDataBytes = Base64.getUrlDecoder().decode(form.getResponse().getAuthenticatorData());
            byte[] signatureBytes = Base64.getUrlDecoder().decode(form.getResponse().getSignature());
            byte[] userHandleBytes = Objects.nonNull(form.getResponse().getUserHandle())
                    ? Base64.getUrlDecoder().decode(form.getResponse().getUserHandle())
                    : null;
            AuthenticatorAssertionResponse assertionResponse = AuthenticatorAssertionResponse.builder()
                    // 将 authenticatorData 转为 ByteArray
                    .authenticatorData(new ByteArray(authenticatorDataBytes))
                    // 将 clientDataJSON 转为 ByteArray
                    .clientDataJSON(new ByteArray(clientDataJsonBytes))
                    // 将 signature 转为 ByteArray
                    .signature(Objects.isNull(signatureBytes) ? null : new ByteArray(signatureBytes))
                    // userHandle（如果有的话）
                    .userHandle(Objects.nonNull(userHandleBytes) ? new ByteArray(userHandleBytes) : ByteArray.fromBase64Url(form.getId()))
                    .build();

            // 使用 builder 构造 PublicKeyCredential
            PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> credential =
                    PublicKeyCredential.<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>builder()
                            .id(ByteArray.fromBase64Url(form.getId()))
                            .response(assertionResponse)
                            .clientExtensionResults(ClientAssertionExtensionOutputs.builder().build())
                            .build();

            AssertionResult result = relyingParty.finishAssertion(FinishAssertionOptions.builder()
                    .request(assertionRequest)
                    .response(credential)
                    .build());
            return new ResultVo("success");
        }catch (Exception e) {
            // 其他系统异常
            log.info("Unexpected error during assertion verification", e);
        }
        return new ResultVo("fail");
    }
}
